﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Smile.Network.Http
{
    /// <summary>Object to perform a download operation.</summary>
    ///
    /// <remarks>
    ///     IHttpDownload is designed for long-term transfer operations for resources like video,
    ///     music, and large images. For short-term operations involving transfers of smaller
    ///     resources (i.e. a couple KB), the use of IHttpRequest objects is recommended.
    /// </remarks>
    ///
    /// <example>
    ///     var download = new HttpDownloader();
    ///     var response = await request.DownloadAsync("http://www.kittyful.com/img_xxx.jpg");
    /// </example>
    public class HttpDownloader : IHttpDownloader 
    {
        // @Events
        public event EventHandler<DownloadInfo> DownloadProgressChanged;


        // @Public
        public async Task<HttpResponse> DownloadAsync(string uri)
        {
            return await DownloadAsync(new Uri(uri));
        }

        public async Task<HttpResponse> DownloadAsync(Uri uri)
        {
            var task = Task<WebResponse>.Run(() =>
            {
                _requestFinished.Reset();

                _request = (HttpWebRequest)HttpWebRequest.Create(uri);
                _request.Method = "GET";
                _request.BeginGetResponse(new AsyncCallback(RequestCallback), _request);

                _requestFinished.WaitOne();
                return _webResponse;
            });

            return await task;
        }


        // @Private
        HttpWebRequest              _request;
        HttpResponse                _webResponse = new HttpResponse();
        ManualResetEvent            _requestFinished = new ManualResetEvent(false);

        void RequestCallback(IAsyncResult asynchronousResult)
        {
            _request = (HttpWebRequest)asynchronousResult.AsyncState;
            _webResponse = new HttpResponse();
      
            try
            {
                var response = (HttpWebResponse)_request.EndGetResponse(asynchronousResult);
                ReadResponse(response);
            }
            catch (WebException e)
            {
                ReadResponse((HttpWebResponse)e.Response);
            }
        }

        void ReadResponse(HttpWebResponse response)
        {
            if (response != null && response.StatusCode == HttpStatusCode.OK)
            {
                // Setting download data.
                _webResponse.Status = response.StatusCode;
                DownloadInfo downloadInfo = new DownloadInfo(response);
                byte[] content = new byte[downloadInfo.Size];
                int blockSize;
                if (downloadInfo.Size > 100)
                {
                    blockSize = downloadInfo.Size / 100;
                }
                else
                {
                    blockSize = downloadInfo.Size;
                }

                // Reading the data.
                using (Stream streamResponse = response.GetResponseStream())
                {
                    byte[] buffer = new byte[blockSize];
                    int readed;
                    while ((readed = streamResponse.Read(buffer, 0, blockSize)) > 0)
                    {
                        OnDownloadProgressChanged(downloadInfo);
                        Buffer.BlockCopy(buffer, 0, content, downloadInfo.Downloaded, readed);
                        downloadInfo.Downloaded += readed;
                        ++downloadInfo.Progress;
                    }
                    downloadInfo.Progress = 100;
                    OnDownloadProgressChanged(downloadInfo);

                    _webResponse.ContentBytes = content;
                }
            }

            _requestFinished.Set();
        }

        void OnDownloadProgressChanged(DownloadInfo downloadInfo)
        {
            if (DownloadProgressChanged != null)
            {
                DownloadProgressChanged(this, downloadInfo);
            }
        }
    }
}
